package com.moli.iov.notify.communication.template;

import javax.el.PropertyNotFoundException;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 *  Resolve matcher Communication Template.
 *
 * @author OAK
 * @version 3.0
 * @since 2019/10/24 11:17 AM.
 *
 */
public interface ResolveMatcher extends Builder {

    /**
     * Match pattern const variables.
     */
    final String PATTERN = "\\{[A-Za-z_]{1,}\\w{1,}\\}";

    /**
     * Get method prefix const variables.
     */
    final String METHOD_PREFIX = "get";

    /**
     * Match whether it is currently <font size="5" color="#008000"><b>&quot;&lt;T&gt;&quot;</b></font>'s
     * {@link Serializable} Generic object or a {@link java.util.Map} interface.
     * @param <T> Generic type.
     * @return Match currently <font size="5" color="#008000"><b>&quot;&lt;T&gt;&quot;</b></font>'s
     * {@link Serializable} Generic object or a {@link java.util.Map} interface.
     */
    default <T> Predicate<T> match(){
       return t -> (isMap(t) || Serializable.class.isAssignableFrom(t.getClass()));
    }

    /**
     * Determine if <font size="5" color="#008000"><b>&quot;&lt;T&gt;&quot;</b></font>'s
     * a {@link java.util.Map} interface.
     * @param t Generic object.
     * @param <T> Generic type.
     * @return <font size="5" color="#008000"><b>&quot;&lt;T&gt;&quot;</b></font>'s
     * {@link java.util.Map} interface.
     */
    default <T> boolean isMap(T t){
        return (t instanceof Map);
    }

    /**
     * The composite current input and variables are populated into the template and output.
     * @param input input.
     * @param variables variables collection.
     * @param <T> Generic type.
     * @return Combined template.
     */
    default <T> String compose(String input, T variables){
        if(isMap(variables)){
            return resolve(input, (Map<String, String>) variables);
        }
        return resolve(input, variables);
    }

    /**
     * According input and variables to Resolve construct compile communication template.
     * @param input input.
     * @param variables Variables collection.
     * @return After to resolve value.
     */
    default String resolve(String input, Map<String, String> variables){
        StringBuffer buffer = new StringBuffer();
        Pattern pattern = Pattern.compile(PATTERN);
        Matcher matcher = pattern.matcher(input);
        while(matcher.find()){
            String group = matcher.group(), propertyName = group.substring(1, group.length() - 1);
            if(!variables.containsKey(propertyName)){
                throw new PropertyNotFoundException("Current property [" + propertyName + "] not found.");
            }
            String value = variables.get(propertyName);
            if(value == null){
                matcher.appendReplacement(buffer, "");
            }else{
                matcher.appendReplacement(buffer, value);
            }
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    /**
     * Resolve current input.
     * @param input input.
     * @param variables Variables collection.
     * @return After to resolve value.
     */
    default <T> String resolve(String input, T variables){
        StringBuffer buffer = new StringBuffer();
        Pattern pattern = Pattern.compile(PATTERN);
        Matcher matcher = pattern.matcher(input);
        Class<?> clazz = variables.getClass();
        while(matcher.find()){
            String group = matcher.group(), propertyName = group.substring(1, group.length() - 1);
            try {
                MethodType methodType = MethodType.methodType(String.class);
                String methodName = METHOD_PREFIX + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                MethodHandle methodHandle = MethodHandles.lookup().findVirtual(clazz, methodName, methodType);
                if(methodHandle == null){
                    throw new PropertyNotFoundException("Current property [" + propertyName + "] not found.");
                }
                Object value = methodHandle.invoke(variables);
                if(value == null){
                    matcher.appendReplacement(buffer, "");
                }else{
                    matcher.appendReplacement(buffer, value.toString());
                }
            } catch (NoSuchFieldException e) {
                throw new PropertyNotFoundException("Current property [" + propertyName + "] not found.", e);
            } catch (IllegalAccessException e) {
                throw new PropertyNotFoundException("Current property [" + propertyName + "] not found.", e);
            } catch (Throwable throwable) {
                throw new PropertyNotFoundException("Current property [" + propertyName + "] not found.", throwable);
            }
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

}
